package com.oristartech.netty.server.starter;

import com.oristartech.netty.server.config.NettyServerConfig;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.util.concurrent.ThreadFactory;

/**
 ** Netty服务端启动服务
 *
 * @author: chenfenghai
 * @version: 1.0
 * @date: 2020-03-10 10:06
 * @blame Team
 */
@Slf4j
@Component
public class NettyServerStarter {

    @Resource
    NettyServerConfig nettyServerConfig;

    /**
     ** 通道初始化
     */
    @Resource
    NettyServerChannelInitializer nettyServerChannelInitializer;

    /**
     ** Netty服务端启动
     */
    public void start(){
        // IP地址，从配置中获取
        InetSocketAddress socketAddress = new InetSocketAddress(nettyServerConfig.getPort());
        // new 一个主线程组，并定义线程名称，方便在日志中查看
        // nThreads为0表示获取默认值，即Java虚拟机的可用的处理器数量 * 2
        ThreadFactory bossFactory = new DefaultThreadFactory("n.s.boss");
        EventLoopGroup bossGroup = new NioEventLoopGroup(0, bossFactory);
        // new 一个工作线程组，并定义线程名称，方便在日志中查看
        // nThreads为0表示获取默认值，即Java虚拟机的可用的处理器数量 * 2
        ThreadFactory workerFactory = new DefaultThreadFactory("n.s.work");
        EventLoopGroup workGroup = new NioEventLoopGroup(0, workerFactory);
        try {
            // netty服务端应用入口
            ServerBootstrap bootstrap = new ServerBootstrap()
                    // bossGroup是负责接收请求，Reactor的核心在于一个线程可以处理多个请求，代码核心是for循环
                    // workGroup负责不同连接的读写请求，采用轮询，有数据到就开线程处理，没有数据就处理下一个；
                    // bossGroup负责连接，workGroup负责处理I/O请求
                    .group(bossGroup, workGroup)
                    // 注册NioServerSocketChannel通道
                    .channel(NioServerSocketChannel.class)
                    .localAddress(socketAddress)
                    // 添加handler，监听Channel的各种动作以及状态的改变，包括连接，绑定，接收消息等
                    // handler在初始化时就会执行
                    .handler(new LoggingHandler(LogLevel.INFO))
                    // 添加childHandler，用来监听已经连接的客户端的Channel的动作和状态
                    // childHandler会在客户端成功connect后才执行
                    .childHandler(nettyServerChannelInitializer)
                    // 用于构造服务端套接字ServerSocket对象，标识当服务器请求处理线程全满时，用于临时存放已完成三次握手的请求的队列的最大长度。
                    // 用来初始化服务端可连接队列
                    // 服务端处理客户端连接请求是按顺序处理的，所以同一时间只能处理一个客户端连接，
                    // 多个客户端来的时候，服务端将不能处理的客户端连接请求放在队列中等待处理，
                    // SO_BACKLOG参数指定了队列的大小。如果未设置或所设置的值小于1，Java将使用默认值50。
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    // SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口，即使以前建立的将此端口用做他们的本地端口的连接仍存在。
                    // 这通常是重启监听服务器时出现，若不设置此选项，则bind时将出错。
                    // SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例，只要每个实例捆绑一个不同的本地IP地址即可。
                    // 对于TCP，我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
                    // SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上，只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
                    // SO_REUSEADDR允许完全重复的捆绑：当一个IP地址和端口绑定到某个套接口上时，还允许此IP地址和端口捆绑到另一个套接口上。
                    // 一般来说，这个特性仅在支持多播的系统上才有，而且只对UDP套接口而言（TCP不支持多播）
                    .option(ChannelOption.SO_REUSEADDR, true)
                    // 是否启用心跳保活机制。在双方TCP套接字建立连接后（即都进入ESTABLISHED状态）
                    // 并且在两个小时左右上层没有任何数据传输的情况下，这套机制才会被激活。
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 绑定端口,开始接收进来的连接
            ChannelFuture future = bootstrap.bind(socketAddress).sync();
            log.info("Netty服务端启动成功，开始监听端口: {}", socketAddress.getPort());
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("Netty服务端启动失败", e);
        } finally {
            //关闭主线程组
            bossGroup.shutdownGracefully();
            //关闭工作线程组
            workGroup.shutdownGracefully();
        }
    }
}
